﻿using Microsoft.Practices.ServiceLocation;
using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Markup;
using System.Windows.Navigation;
using UltimateNavigationFramework.Authorisation;
using UltimateNavigationFramework.Loaders;

namespace UltimateNavigationFramework.UriNavigation
{
    [ContentProperty("ErrorPages")]
    public class UltimateNavigationFrame : ContentControl, INavigate, ILoadStatus
    {
        #region Private static fields
        private bool _isRootSet = false;
        #endregion

        #region Private fields
        private bool _loaded;
        private EventHandler<NavigationStateChangedEventArgs> _weakRefEventHandler;
        private Uri _deferredNavigation;
        private string _title;
        #endregion

        #region IsDesignTime Check
        private static bool _isDesignTime
        {
            get
            {
                return DesignerProperties.IsInDesignTool;
            }
        }
        #endregion

        #region Dependency Properties
        public static readonly DependencyProperty SourceProperty = DependencyProperty.Register("Source",
            typeof(Uri),
            typeof(UltimateNavigationFrame),
            new PropertyMetadata(new PropertyChangedCallback(UltimateNavigationFrame.SourcePropertyChanged)));

        public static readonly DependencyProperty UriMapperProperty = DependencyProperty.Register("UriMapper",
            typeof(UltimateNavigationUriMapper),
            typeof(UltimateNavigationFrame),
            (PropertyMetadata)null);

        public static readonly DependencyProperty CanGoBackProperty = DependencyProperty.Register("CanGoBack",
            typeof(bool),
            typeof(UltimateNavigationFrame),
            null);

        public static readonly DependencyProperty CanGoForwardProperty = DependencyProperty.Register("CanGoForward",
            typeof(bool),
            typeof(UltimateNavigationFrame),
            null);

        public static readonly DependencyProperty CurrentSourceProperty = DependencyProperty.Register("CurrentSource",
            typeof(Uri),
            typeof(UltimateNavigationFrame),
            null);

        public static readonly DependencyProperty IsBusyProperty = DependencyProperty.Register("IsBusy",
            typeof(bool),
            typeof(UltimateNavigationFrame),
            new PropertyMetadata(false));

        public static readonly DependencyProperty ProgressProperty = DependencyProperty.Register("Progress",
            typeof(double),
            typeof(UltimateNavigationFrame),
            new PropertyMetadata(0d));

        public static readonly DependencyProperty StatusTextProperty = DependencyProperty.Register("StatusText",
            typeof(string),
            typeof(UltimateNavigationFrame),
            new PropertyMetadata(null));

        public static readonly DependencyProperty AuthoriserProperty = DependencyProperty.Register("Authoriser",
            typeof(INavigationAuthoriser),
            typeof(UltimateNavigationFrame),
            new PropertyMetadata(null));
        #endregion

        #region Properties
        public bool CanGoBack
        {
            get
            {
                return (bool)this.GetValue(UltimateNavigationFrame.CanGoBackProperty);
            }
            internal set
            {
                // Will be set on the service
                this.SetValue(UltimateNavigationFrame.CanGoBackProperty, value);
            }
        }

        public bool CanGoForward
        {
            get
            {
                return (bool)this.GetValue(UltimateNavigationFrame.CanGoForwardProperty);
            }
            internal set
            {
                // Will be set on the service
                this.SetValue(UltimateNavigationFrame.CanGoForwardProperty, value);
            }
        }

        public Uri CurrentSource
        {
            get
            {
                return this.GetValue(UltimateNavigationFrame.CurrentSourceProperty) as Uri;
            }
            internal set
            {
                // Will be set on the service
                this.SetValue(UltimateNavigationFrame.CurrentSourceProperty, value);
            }
        }

        public Uri Source
        {
            get
            {
                return this.GetValue(UltimateNavigationFrame.SourceProperty) as Uri;
            }
            set
            {
                this.SetValue(UltimateNavigationFrame.SourceProperty, (object)value);
            }
        }

        public UltimateNavigationUriMapper UriMapper
        {
            get
            {
                return this.GetValue(UltimateNavigationFrame.UriMapperProperty) as UltimateNavigationUriMapper;
            }
            set
            {
                this.SetValue(UltimateNavigationFrame.UriMapperProperty, (object)value);
            }
        }

        /// <summary>
        /// The authoriser object
        /// </summary>
        public INavigationAuthoriser Authoriser
        {
            get
            {
                return this.GetValue(UltimateNavigationFrame.AuthoriserProperty) as INavigationAuthoriser;
            }
            set
            {
                this.SetValue(UltimateNavigationFrame.AuthoriserProperty, (object)value);
            }
        }

        public bool IsRoot
        {
            get;
            set;
        }

        /// <summary>
        /// This basically represents the region name
        /// </summary>
        public string Title
        {
            get
            {
                return _title;
            }
            internal set
            {
                if (value != _title)
                {
                    _title = value;
                    // This is to ensure that the region is first set before navigating
                    this.Dispatcher.BeginInvoke(() =>
                        {
                            if (_deferredNavigation != (Uri)null)
                            {
                                _navigationService.NavigateUrl(_deferredNavigation.OriginalString, this._title, this, true);
                                _deferredNavigation = (Uri)null;
                            }
                            else if (this.Source != (Uri)null)
                            {
                                this.Navigate(this.Source);
                            }
                        });
                }
            }
        }

        /// <summary>
        /// Gets a boolean indicating whether it is in the process of downloading or loading a page.
        /// </summary>
        public bool IsBusy
        {
            get
            {
                return (bool)this.GetValue(IsBusyProperty);
            }
            private set
            {
                this.SetValue(IsBusyProperty, value);
            }
        }

        /// <summary>
        /// Gets the progress of any ongoing downloads it is performing.
        /// </summary>
        public double Progress
        {
            get
            {
                return (double)this.GetValue(ProgressProperty);
            }
            private set
            {
                this.SetValue(ProgressProperty, value);
            }
        }

        /// <summary>
        /// Gets a string indicating the status info
        /// </summary>
        public string StatusText
        {
            get
            {
                return (string)this.GetValue(StatusTextProperty);
            }
            private set
            {
                this.SetValue(StatusTextProperty, value);
            }
        }

        bool ILoadStatus.IsBusy
        {
            get
            {
                return this.IsBusy;
            }
            set
            {
                this.IsBusy = value;
            }
        }

        double ILoadStatus.Progress
        {
            get
            {
                return this.Progress;
            }
            set
            {
                this.Progress = value;
            }
        }

        string ILoadStatus.StatusText
        {
            get
            {
                return this.StatusText;
            }
            set
            {
                this.StatusText = value;
            }
        }

        /// <summary>
        /// The set of IErrorPages that will be used to determine what Uri to load when an error is encountered.
        /// </summary>
        public DependencyObjectCollection<IErrorPage> ErrorPages { get; private set; }
        #endregion

        #region Navigation Events
        public event NavigatedEventHandler Navigated
        {
            add
            {
                _navigationService.Navigated += value;
            }
            remove
            {
                _navigationService.Navigated -= value;
            }
        }

        public event NavigatingCancelEventHandler Navigating
        {
            add
            {
                _navigationService.Navigating += value;
            }
            remove
            {
                _navigationService.Navigating -= value;
            }
        }

        public event UltimateNavigationFailedEventHandler NavigationFailed
        {
            add
            {
                _navigationService.NavigationFailed += value;
            }
            remove
            {
                _navigationService.NavigationFailed -= value;
            }
        }

        public event NavigationStoppedEventHandler NavigationStopped
        {
            add
            {
                _navigationService.NavigationStopped += value;
            }
            remove
            {
                _navigationService.NavigationStopped -= value;
            }
        }
        #endregion

        #region UltimateNavigationFrameLoadingService
        /// <summary>
        /// Static instance of the navigation service
        /// </summary>
        private static IUltimateNavigationFrameLoadingService _navigationService
        {
            get
            {
                if (_isDesignTime)
                    return null;

                return ServiceLocator.Current.GetInstance<IUltimateNavigationFrameLoadingService>();
            }
        }
        #endregion

        #region Dependency Property Changed Events
        /// <summary>
        /// Source changed, will fire navigate
        /// </summary>
        /// <param name="depObj"></param>
        /// <param name="e"></param>
        private static void SourcePropertyChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
        {
            var frame = depObj as UltimateNavigationFrame;

            if (frame != null && !UltimateNavigationFrame._isDesignTime && (frame._loaded))
                frame.Navigate(e.NewValue as Uri);

            if (!UltimateNavigationFrame._isDesignTime)
                return;

            //  Design time only
            if (e.NewValue != null && frame != null)
            {
                frame.Content = (object)string.Format((IFormatProvider)CultureInfo.InvariantCulture, "Default Content", new object[1]
                                                        {
                                                            (object) e.NewValue.ToString()
                                                        });
            }
            else if (frame != null)
                frame.Content = (object)frame.GetType().Name;
        }
        #endregion

        #region Constructor
        public UltimateNavigationFrame()
        {
            this.HorizontalContentAlignment = HorizontalAlignment.Stretch;
            this.VerticalContentAlignment = VerticalAlignment.Stretch;

            if (_isDesignTime)
                return;

            this.ErrorPages = new DependencyObjectCollection<IErrorPage>();
            this.Loaded += UltimateNavigationFrame_Loaded;
        }

        /// <summary>
        /// Destructor
        /// </summary>
        ~UltimateNavigationFrame()
        {
            if (this._weakRefEventHandler == null)
                return;

            Deployment.Current.Dispatcher.BeginInvoke((Action)(() => Application.Current.Host.NavigationStateChanged -= this._weakRefEventHandler));
        }
        #endregion

        #region Loaded Event
        /// <summary>
        /// Frame Loaded Event, will attach the navigation state changed event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UltimateNavigationFrame_Loaded(object sender, RoutedEventArgs e)
        {
            this._loaded = true;

            if (!_isDesignTime && this.IsRoot && !_isRootSet)
            {
                var thisWeak = new WeakReference((object)this);

                // Attaches the weak navigation event handler
                // Occurs when navigation state changed
                this._weakRefEventHandler = (EventHandler<NavigationStateChangedEventArgs>)((weakSender, args) =>
                {
                    // this.Navigate(new Uri(Application.Current.Host.NavigationState, UriKind.Relative));

                    // Name will be the region name and will be set by the adapter/behavior. as this will be invoked via the url, we flag the suppressJournalAdd to true
                    _navigationService.NavigateUrl(Application.Current.Host.NavigationState, this.Title, this, true);
                });

                try
                {
                    Application.Current.Host.NavigationStateChanged += this._weakRefEventHandler;
                }
                catch (InvalidOperationException ex)
                {
                    // Do nothing for now
                }

                // If the current url is not empty we invoke a navigate
                if (Application.Current.Host.NavigationState.ToLower() !=
                    (this.CurrentSource != null ? this.CurrentSource.OriginalString.ToLower() : String.Empty))
                {
                    _deferredNavigation = new Uri(Application.Current.Host.NavigationState, UriKind.Relative);
                }

                _isRootSet = true;
            }
        }
        #endregion

        #region INavigate Implementation
        /// <summary>
        /// This will be called from the browser url navigation
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public bool Navigate(Uri source)
        {
            // Name will be the region name and will be set by the adapter/behavior            
            System.Windows.Browser.HtmlPage.Window.Navigate(new Uri(String.Format("#{0}", source.OriginalString), UriKind.Relative));

            return true;
        }

        /// <summary>
        /// Go Back
        /// </summary>
        public void GoBack()
        {
            _navigationService.GoBack(this);
        }

        /// <summary>
        /// Go Forward
        /// </summary>
        public void GoForward()
        {
            _navigationService.GoForward(this);
        }
        #endregion
    }
}
